﻿package com.sgcc.nfc.lock.presentation.rooms

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import com.sgcc.nfc.lock.BuildConfig
import com.sgcc.nfc.lock.R
import com.sgcc.nfc.lock.core.location.CoordinateConverter
import com.sgcc.nfc.lock.databinding.FragmentRoomFormBinding
import com.sgcc.nfc.lock.domain.model.Region
import com.sgcc.nfc.lock.domain.model.RegionLevel
import com.sgcc.nfc.lock.domain.repository.RoomRepository
import com.sgcc.nfc.lock.presentation.rooms.LocationPickerActivity.Companion.EXTRA_LATITUDE
import com.sgcc.nfc.lock.presentation.rooms.LocationPickerActivity.Companion.EXTRA_LONGITUDE
import dagger.hilt.android.AndroidEntryPoint
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject

@AndroidEntryPoint
class RoomFormFragment : Fragment() {

    private var _binding: FragmentRoomFormBinding? = null
    private val binding get() = _binding!!
    private val viewModel: RoomFormViewModel by viewModels()

    private var regionOptions: List<RegionOption> = emptyList()
    private var selectedRegionId: Long? = null
    private var latitude: Double? = null
    private var longitude: Double? = null
    private var hasInitializedRoom = false
    private var autoFillLocationManager: LocationManager? = null
    private var autoFillLocationListener: LocationListener? = null
    private var autoFilledAddress: String? = null
    private var addressManuallyEdited = false
    private var suppressAddressWatcher = false
    private var geocodeJob: Job? = null
    private val geocodeClient by lazy {
        OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.SECONDS)
            .readTimeout(5, TimeUnit.SECONDS)
            .writeTimeout(5, TimeUnit.SECONDS)
            .build()
    }

    private val pickLocationLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            val data = result.data
            val lat = data?.getDoubleExtra(EXTRA_LATITUDE, Double.NaN)
            val lng = data?.getDoubleExtra(EXTRA_LONGITUDE, Double.NaN)
            if (lat != null && !lat.isNaN() && lng != null && !lng.isNaN()) {
                updateLocationFields(lat, lng)
            }
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentRoomFormBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupListeners()
        setupAddressInputWatcher()
        observeState()
    }

    private fun setupListeners() {
        binding.pickLocationButton.setOnClickListener {
            val intent = LocationPickerActivity.createIntent(requireContext(), latitude, longitude)
            pickLocationLauncher.launch(intent)
        }
        binding.cancelButton.setOnClickListener { requireActivity().onBackPressedDispatcher.onBackPressed() }
        binding.saveButton.setOnClickListener { submitForm() }
    }

    private fun setupAddressInputWatcher() {
        binding.addressInput.addTextChangedListener { text ->
            if (suppressAddressWatcher) return@addTextChangedListener
            val current = text?.toString().orEmpty()
            addressManuallyEdited = current.isNotBlank() && current != autoFilledAddress
        }
    }

    private fun observeState() {
        viewModel.uiState.observe(viewLifecycleOwner) { state ->
            binding.formLoading.isVisible = state.isLoading || state.isSubmitting
            binding.saveButton.isEnabled = !state.isSubmitting
            binding.cancelButton.isEnabled = !state.isSubmitting
            binding.pickLocationButton.isEnabled = !state.isSubmitting
            if (state.error != null) {
                Snackbar.make(binding.root, state.error, Snackbar.LENGTH_LONG).show()
            }
            if (!state.isLoading) {
                setupRegionDropdown(state.regions)
                if (!hasInitializedRoom && state.room != null) {
                    populateForm(state.room)
                    hasInitializedRoom = true
                } else if (!hasInitializedRoom && !viewModel.isEdit) {
                    hasInitializedRoom = true
                    tryPopulateCurrentLocation()
                }
            }
        }
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.events.collect { event ->
                    when (event) {
                        is RoomFormEvent.ShowMessage -> Snackbar.make(binding.root, event.message, Snackbar.LENGTH_LONG).show()
                        is RoomFormEvent.SubmitSuccess -> {
                            val messageRes = when (event.type) {
                                RoomFormEvent.OperationType.CREATE -> R.string.room_create_success
                                RoomFormEvent.OperationType.UPDATE -> R.string.room_update_success
                            }
                            Snackbar.make(binding.root, getString(messageRes), Snackbar.LENGTH_SHORT).show()
                            notifyFormCompleted()
                            requireActivity().onBackPressedDispatcher.onBackPressed()
                        }
                    }
                }
            }
        }
    }

    private fun setupRegionDropdown(regions: List<Region>) {
        if (regions.isEmpty()) {
            binding.regionDropdown.setAdapter(null)
            binding.regionDropdown.text?.clear()
            binding.regionLayout.isEnabled = false
            binding.regionDropdown.isEnabled = false
            binding.regionLayout.error = getString(R.string.room_form_region_empty)
            binding.saveButton.isEnabled = false
            return
        }
        regionOptions = buildRegionOptions(regions)
        if (regionOptions.isEmpty()) {
            binding.regionDropdown.setAdapter(null)
            binding.regionDropdown.text?.clear()
            binding.regionLayout.isEnabled = false
            binding.regionDropdown.isEnabled = false
            binding.regionLayout.error = getString(R.string.room_form_region_empty)
            binding.saveButton.isEnabled = false
            return
        }
        val canSubmit = viewModel.uiState.value?.isSubmitting?.not() ?: true
        binding.saveButton.isEnabled = canSubmit
        binding.regionLayout.isEnabled = true
        binding.regionDropdown.isEnabled = true
        binding.regionLayout.isEndIconVisible = regionOptions.size > 1
        binding.regionLayout.setEndIconOnClickListener { binding.regionDropdown.showDropDown() }
        binding.regionDropdown.setOnClickListener { binding.regionDropdown.showDropDown() }
        binding.regionDropdown.setOnFocusChangeListener { _, hasFocus ->
            if (hasFocus) binding.regionDropdown.showDropDown()
        }
        binding.regionLayout.error = null

        val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, regionOptions.map { it.label })
        binding.regionDropdown.setAdapter(adapter)
        binding.regionDropdown.setOnItemClickListener { _, _, position, _ ->
            selectedRegionId = regionOptions.getOrNull(position)?.id
            binding.regionLayout.error = null
        }
        if (regionOptions.size == 1 && selectedRegionId == null && !viewModel.isEdit) {
            regionOptions.firstOrNull()?.let { option ->
                selectedRegionId = option.id
                binding.regionDropdown.setText(option.label, false)
            }
        }
        if (selectedRegionId != null) {
            val selected = regionOptions.firstOrNull { it.id == selectedRegionId }
            if (selected != null) {
                binding.regionDropdown.setText(selected.label, false)
            } else {
                selectedRegionId = null
                binding.regionDropdown.text?.clear()
            }
        } else if (viewModel.isEdit && viewModel.uiState.value?.room?.regionId != null) {
            val selected = regionOptions.firstOrNull { it.id == viewModel.uiState.value?.room?.regionId }
            selected?.let {
                binding.regionDropdown.setText(it.label, false)
                selectedRegionId = it.id
            }
        }
    }

    private fun populateForm(room: com.sgcc.nfc.lock.domain.model.Room) {
        binding.formTitle.setText(R.string.room_form_title_edit)
        binding.nameInput.setText(room.roomName)
        suppressAddressWatcher = true
        binding.addressInput.setText(room.address)
        binding.addressInput.setSelection(room.address?.length ?: 0)
        suppressAddressWatcher = false
        autoFilledAddress = room.address
        addressManuallyEdited = false

        latitude = room.latitude
        longitude = room.longitude
        val lat = room.latitude
        val lng = room.longitude
        if (lat != null) {
            binding.latitudeInput.setText(String.format(Locale.CHINA, "%.6f", lat))
            binding.latitudeLayout.error = null
        }
        if (lng != null) {
            binding.longitudeInput.setText(String.format(Locale.CHINA, "%.6f", lng))
            binding.longitudeLayout.error = null
        }
        if (room.address.isNullOrBlank() && lat != null && lng != null) {
            requestReverseGeocode(lat, lng)
        }
        selectedRegionId = room.regionId
    }

    private fun submitForm() {
        val name = binding.nameInput.text?.toString()?.trim().orEmpty()
        val regionId = selectedRegionId
        val lat = latitude
        val lng = longitude
        var hasError = false
        if (name.isBlank()) {
            binding.nameLayout.error = getString(R.string.room_validation_name)
            hasError = true
        } else {
            binding.nameLayout.error = null
        }
        if (regionId == null) {
            binding.regionLayout.error = getString(R.string.room_validation_region)
            hasError = true
        } else {
            binding.regionLayout.error = null
        }
        if (lat == null || lng == null) {
            binding.latitudeLayout.error = getString(R.string.room_validation_location)
            binding.longitudeLayout.error = getString(R.string.room_validation_location)
            hasError = true
        } else {
            binding.latitudeLayout.error = null
            binding.longitudeLayout.error = null
        }
        if (hasError) return

        if (viewModel.isEdit) {
            val roomId = viewModel.uiState.value?.room?.roomId ?: return
            val params = RoomRepository.UpdateRoomParams(
                roomId = roomId,
                roomName = name,
                regionId = regionId!!,
                longitude = lng!!,
                latitude = lat!!,
                address = binding.addressInput.text?.toString()?.trim()
            )
            viewModel.updateRoom(params)
        } else {
            val params = RoomRepository.CreateRoomParams(
                roomName = name,
                regionId = regionId!!,
                longitude = lng!!,
                latitude = lat!!,
                address = binding.addressInput.text?.toString()?.trim()
            )
            viewModel.createRoom(params)
        }
    }

    private fun notifyFormCompleted() {
        findNavController().previousBackStackEntry?.savedStateHandle?.set(RESULT_KEY_ROOM_UPDATED, true)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        cleanupAutoFillLocation()
        geocodeJob?.cancel()
        geocodeJob = null
        _binding = null
    }

    private fun tryPopulateCurrentLocation() {
        if (latitude != null && longitude != null) return
        val context = context ?: return
        if (!hasLocationPermission(context)) return
        val manager = (autoFillLocationManager ?: context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager)
            ?: return
        autoFillLocationManager = manager

        val providers = listOf(LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER)
        providers.forEach { provider ->
            if (manager.isProviderEnabled(provider)) {
                @Suppress("MissingPermission")
                val lastLocation = runCatching { manager.getLastKnownLocation(provider) }.getOrNull()
                if (lastLocation != null) {
                    updateLocationFromWgs(lastLocation.latitude, lastLocation.longitude)
                    return
                }
            }
        }

        if (autoFillLocationListener != null) return
        val provider = providers.firstOrNull { manager.isProviderEnabled(it) } ?: return
        val listener = object : LocationListener {
            override fun onLocationChanged(location: Location) {
                updateLocationFromWgs(location.latitude, location.longitude)
                cleanupAutoFillLocation()
            }

            override fun onProviderDisabled(provider: String) = Unit
            override fun onProviderEnabled(provider: String) = Unit
            @Deprecated("Deprecated in Java")
            override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) = Unit
        }
        autoFillLocationListener = listener
        @Suppress("MissingPermission")
        manager.requestSingleUpdate(provider, listener, Looper.getMainLooper())
    }

    private fun updateLocationFields(lat: Double, lng: Double) {
        latitude = lat
        longitude = lng
        _binding?.let { binding ->
            binding.latitudeInput.setText(String.format(Locale.CHINA, "%.6f", lat))
            binding.longitudeInput.setText(String.format(Locale.CHINA, "%.6f", lng))
            binding.latitudeInput.setSelection(binding.latitudeInput.text?.length ?: 0)
            binding.longitudeInput.setSelection(binding.longitudeInput.text?.length ?: 0)
            binding.latitudeLayout.error = null
            binding.longitudeLayout.error = null
        }
        requestReverseGeocode(lat, lng)
    }

    private fun updateLocationFromWgs(lat: Double, lng: Double) {
        val (convertedLat, convertedLng) = CoordinateConverter.wgs84ToGcj02(lat, lng)
        updateLocationFields(convertedLat, convertedLng)
    }

    private fun requestReverseGeocode(lat: Double, lng: Double) {
        if (addressManuallyEdited) return
        val apiKey = BuildConfig.AMAP_API_KEY.takeIf { it.isNotBlank() } ?: return
        geocodeJob?.cancel()
        geocodeJob = viewLifecycleOwner.lifecycleScope.launch {
            val locationParam = String.format(Locale.US, "%.6f,%.6f", lng, lat)
            val url = HttpUrl.Builder()
                .scheme("https")
                .host("restapi.amap.com")
                .addPathSegments("v3/geocode/regeo")
                .addQueryParameter("key", apiKey)
                .addQueryParameter("location", locationParam)
                .addQueryParameter("extensions", "base")
                .addQueryParameter("radius", "200")
                .build()
            val request = Request.Builder()
                .url(url)
                .get()
                .build()
            val address = withContext(Dispatchers.IO) {
                runCatching {
                    geocodeClient.newCall(request).execute().use { response ->
                        if (!response.isSuccessful) return@use null
                        val body = response.body?.string() ?: return@use null
                        parseAddress(body)
                    }
                }.getOrNull()
            }
            if (!address.isNullOrBlank()) {
                applyAutoAddress(address)
            }
        }
    }

    private fun parseAddress(payload: String): String? {
        return runCatching {
            val root = JSONObject(payload)
            if (root.optInt("status") != 1) return@runCatching null
            val regeocode = root.optJSONObject("regeocode") ?: return@runCatching null
            val formatted = regeocode.optString("formatted_address").takeIf { it.isNotBlank() }
            val fallback = regeocode.optJSONObject("addressComponent")
                ?.optString("district")
                ?.takeIf { it.isNotBlank() }
            formatted ?: fallback
        }.getOrNull()
    }

    private fun applyAutoAddress(address: String) {
        if (addressManuallyEdited) return
        suppressAddressWatcher = true
        autoFilledAddress = address.trim().takeIf { it.isNotEmpty() }
        _binding?.addressInput?.let { input ->
            val value = autoFilledAddress.orEmpty()
            input.setText(value)
            input.setSelection(value.length)
        }
        suppressAddressWatcher = false
        addressManuallyEdited = false
    }

    private fun cleanupAutoFillLocation() {
        autoFillLocationListener?.let { listener ->
            autoFillLocationManager?.removeUpdates(listener)
        }
        autoFillLocationListener = null
        autoFillLocationManager = null
    }

    private fun hasLocationPermission(context: Context): Boolean =
        ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED

    private fun buildRegionOptions(regions: List<Region>): List<RegionOption> {
        val options = mutableListOf<RegionOption>()
        fun append(region: Region, depth: Int) {
            val level = RegionLevel.fromValue(region.regionType ?: region.level)
            if (level == RegionLevel.STATION) {
                region.regionId?.let { id ->
                    val label = buildString {
                        repeat(depth) { append("   ") }
                        append(region.regionName)
                    }
                    options += RegionOption(id, label)
                }
            }
            region.children.forEach { child -> append(child, depth + 1) }
        }
        regions.forEach { append(it, 0) }
        return options
    }

    private data class RegionOption(val id: Long, val label: String)

    companion object {
        const val RESULT_KEY_ROOM_UPDATED = "room_updated"
    }
}
